﻿#if NETFX
using Socket = System.Net.Sockets.Socket;

#elif NETFX_CORE || WINDOWS_PHONE
using Socket = Windows.Networking.Sockets.StreamSocket;

#endif
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using XmlSockets.Internal;
using XmlSockets.Internal.AbSystem;
using System.Collections.Generic;



namespace XmlSockets
{
    /// <summary>
    /// Client xmlsocket
    /// </summary>
    public sealed class ClientXmlSocket:IDisposable
    {

        #region Construoctor

        /// <summary>
        /// Constructor
        /// </summary>
        public ClientXmlSocket() : this(new XmlSocketCreateInfo(), SynchronizationContext.Current) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="careteInfo"></param>
        /// <param name="synchronizationContext"></param>
        public ClientXmlSocket(XmlSocketCreateInfo careteInfo, SynchronizationContext synchronizationContext)
        {
            this.XmlSocket = null;
            this.Closed = null;
            this.Received = null;
            this.reaceiveLoopTask = null;
            this.SocketCreateInfo = careteInfo;
            this.synchronizationContext = synchronizationContext;

        }





        #endregion

        #region Finalizer

        /// <summary>
        /// Finalizer
        /// </summary>
        ~ClientXmlSocket()
        {
            Dispose();
        }

        #endregion

        #region Event

        /// <summary>
        /// Handler to be called when the socket is closed
        /// </summary>
        public event ClientXmlSocketCloseHandler Closed;

        /// <summary>
        /// Hander to be called when the socket received message
        /// </summary>
        public event ClientXmlSocketReceiveHandler Received;

        #endregion

        #region Property


        /// <summary>
        /// Endpoint that has been assigned to this socket
        /// </summary>
        public IEndPoint LocalEndPoint
        {
            get
            {
                return this.XmlSocket.LocalEndPoint;
            }
        }

        /// <summary>
        /// XmlSocket that has been assigned to this socket
        /// </summary>
        public XmlSocket XmlSocket { get; private set; }

        /// <summary>
        /// Endpoint remote destination
        /// </summary>
        public IEndPoint RemorteEndPoint
        {
            get
            {
                return this.XmlSocket.RemorteEndPoint;
            }
        }




        /// <summary>
        /// Infomation of internal socket
        /// </summary>
        public XmlSocketCreateInfo SocketCreateInfo { get; private set; }

        #endregion

        #region Method

        /// <summary>
        /// Connect to asynchronous
        /// </summary>
        /// <param name="hostName"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.IO.IOException"></exception>
        public async Task ConnectAsync(string hostName, int port)
        {
            if (XmlSocket != null && XmlSocket.Connected) new InvalidOperationException("Already connected");
            XmlSocket = new XmlSocket(InternalSocket.Create(), SocketCreateInfo);

            await XmlSocket.ConnectAsync(hostName, port).ConfigureAwait(false);
            this.receiveLoopStart();

        }

        /// <summary>
        /// Send to asynchronous
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        /// <exception cref="System.Xml.XmlException"></exception>
        public async Task SendAsync(string message)
        {
            await mustConnectedFunc(async () =>
            {
                await XmlSocket.SendAsync(message).ConfigureAwait(false);
            }).ConfigureAwait(false);


        }

        /// <summary>
        /// Send to asynchronous
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        public async Task SendAsync(XElement message)
        {

            await mustConnectedFunc(async () =>
            {
                await XmlSocket.SendAsync(message).ConfigureAwait(false);
            }).ConfigureAwait(false);

        }

        /// <summary>
        /// Send to asynchronous
        /// </summary>
        /// <returns></returns>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        public async Task SendAsync(XmlSocketSendHandler handler)
        {
            await mustConnectedFunc(async () =>
            {
                await XmlSocket.SendAsync(handler).ConfigureAwait(false);
            }).ConfigureAwait(false);


        }

        /// <summary>
        /// Close to this socket
        /// </summary>
        public void Close()
        {
            this.Close(null, "general close");
        }

        /// <summary>
        /// Close to this socket
        /// </summary>
        /// <param name="e"></param>
        /// <param name="message"></param>
        internal void Close(Exception e, string message)
        {
            onClose(e, message);

            close();
        }

        /// <summary>
        /// when connected,return true
        /// </summary>
        public bool Connected
        {
            get
            {
                return XmlSocket != null && XmlSocket.Connected;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            lock (this)
            {
                if (XmlSocket == null) return;
                close();
                Closed = null;
                Received = null;
                reaceiveLoopTask = null;

            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlSocket"></param>
        /// <param name="recieveHandler"></param>
        /// <param name="synchronizationContext"></param>
        /// <returns></returns>
        public static ClientXmlSocket Create(XmlSocket xmlSocket, ClientXmlSocketReceiveHandler recieveHandler, SynchronizationContext synchronizationContext = null)
        {
            if (!xmlSocket.Connected) new IOException("Socket is Not connected");
            ClientXmlSocket client = new ClientXmlSocket(new XmlSocketCreateInfo(), synchronizationContext);
            client.XmlSocket = xmlSocket;
            client.SocketCreateInfo = new XmlSocketCreateInfo();
            client.SocketCreateInfo.MaxMessageSize = xmlSocket.Reader.MaxAMessageSize;
            client.SocketCreateInfo.RecieveBuffer = xmlSocket.Reader.stream.Capacity;
            client.SocketCreateInfo.RecieveTmpBuffer = xmlSocket.Reader.ReceiveTmpBufferSize;
            client.Received += recieveHandler;
            client.receiveLoopStart();
            return client;
        }


#if !PORTABLE_UNITTEST
        /// <summary>
        /// 
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="recieveHandler"></param>
        /// <param name="synchronizationContext"></param>
        /// <returns></returns>
        public static ClientXmlSocket Create(Socket socket, ClientXmlSocketReceiveHandler recieveHandler, SynchronizationContext synchronizationContext = null)
        {
            return Create(XmlSocket.Create(socket), recieveHandler);
        }
#endif

        #endregion

        #region Private


        private  void receiveLoopStart()
        {
            this.reaceiveLoopTask = receiveStartAsync();
        }

        private async Task receiveStartAsync()
        {
            await receiveLoop().ConfigureAwait(false);
        }





        private void onReceive(ClientXmlSocketReceiveEventArgs args)
        {
            
            if (Received != null){
                Received(args);
            }

            
        }




        private void onClose(Exception e, string reason)
        {

            ClientXmlSocketCloseEventArgs closeArgs = new ClientXmlSocketCloseEventArgs(this);
            closeArgs.Reason = reason;
            closeArgs.Exception = e;
            Closed(closeArgs);


            
        }




        private async Task receiveLoop( )
        {

            try
            {

                await mustConnectedFunc(async () =>
                {
                    await Task.Factory.StartNew(async () =>
                    {
                        do{
                            await XmlSocket.ReceiveAsync(messages =>
                            {



                                var args = new ClientXmlSocketReceiveEventArgs(this);
                                var messageList = new List<XElement>();

                                foreach (var message in messages)
                                {


                                    using (message.Reader)
                                    {
                                        messageList.Add(message.ToXElement());
                                    }



                                }

                                args.Messages = messageList;






                                if (synchronizationContext != null)
                                {
                                    synchronizationContext.Post((sender) =>
                                    {
                                        onReceive(args);
                                    }, this);
                                }
                                else
                                {
                                    onReceive(args);
                                }



                            }).ConfigureAwait(false);
                        }while(this.Connected);
                    }).ConfigureAwait(false);
                }).ConfigureAwait(false);
                            
            }
            catch (IOException e)
            {
                if (this.Connected)
                {
                    this.Close(e, e.Message);
                }
                        
            }




       

            

        }



        private void close()
        {
            if (XmlSocket != null)
            {
                XmlSocket.Dispose();
                XmlSocket = null;
            }
        }

        private async Task mustConnectedFunc(Func<Task> action)
        {
            if (!this.Connected) throw new InvalidOperationException("Socket is Not connected");
            await action().ConfigureAwait(false);
        }

        private Task reaceiveLoopTask;

        private SynchronizationContext synchronizationContext;


        
        #endregion
    }
}
